<?php
class FontHelper {

    const
        FONTFORGE_BIN = '/usr/bin/fontforge',
        TTF2EOT_BIN = '/usr/bin/ttf2eot',
        TTF2WOFF_BIN = '/usr/local/bin/ttf2woff',
        TTF2SVG_BIN = '/usr/bin/ttf2svg.pe',
        STDTTF_BIN = '/usr/bin/fix_ttf_name.pe',
        CONVERT_CMD = '%s %s %s',
        CSS_TEMPLATE = '/fonts/template/tmp.css',
        FONT_UPLOAD_PATH = '/fonts/upload',
        FONT_CSS_CACHE = '/fonts/css',
        FONT_CACHE_PATH = '/fonts/cache';

    /**
     * 字符串去重
     * @param $src
     * @return string
     */
    static public function remove_duplicate($src) {
        $src = strip_tags($src);
        return implode('', array_unique(preg_split('//u', $src, -1, PREG_SPLIT_NO_EMPTY)));
    }

    /**
     * 提取字符串hash
     * @param $src 裁剪包含的字符
     * @param $font_name 字体名称
     * @return string
     */
    static public function get_subset_hash($src, $font_name) {
        $src = self::remove_duplicate($src);
        return md5($src.$font_name);
    }

    /**
     * 根据hash 分割文件夹路径
     * @param $hash
     * @return string
     */
    static public function split_hash($hash) {
        $level1 = substr($hash, 0, 1);
        $level2 = substr($hash, 1, 1);
        $level3 = substr($hash, 2, 1);

        return sprintf('%s/%s/%s/%s', self::FONT_CSS_CACHE, $level1, $level2, $level3);
    }

    /**
     * 检查生成的css文件是否已经存在
     * @param $hash
     * @return bool|string
     */
    static public function check_css_cache($hash) {
        $ral_path = self::split_hash($hash).'/'.$hash.'.css';
        $path = Common::getPathOf($ral_path, Common::P_PUBLIC);
        Common::log('check css file cache status, path:'.$path, 'font.log');
        if (file_exists($path)) {
            Common::log('css file cached:'.$path, 'font.log');
            return $ral_path;
        }

        return false;
    }

    /**
     * 裁剪(压缩)字体
     * @param $font_name 字体名称
     * @param $subset 裁剪包含的字符
     * @return bool|string
     */
    static public function subset_font($font_name, $subset) {
        $hash = self::get_subset_hash($subset, $font_name);
        $css_path = self::check_css_cache($hash);
        if ($css_path) {
            return $css_path;
        }
        $fontfile = Common::getPathOf(self::FONT_UPLOAD_PATH.'/'.$font_name.'.ttf', Common::P_PUBLIC);
        if (!file_exists($fontfile)) {
            return false;
        }
        $font_subset = self::subset_ttf($fontfile, $subset, $hash);
        self::fix_ttf_name($font_subset, $font_name);
        $font_eot = str_replace('.ttf', '.eot', $font_subset);
        $font_eot = self::convert_to_eot($font_subset, $font_eot);
        if ($font_eot) {
            $font_woff = str_replace('.ttf', '.woff', $font_subset);
            $font_woff = self::convert_to_woff($font_subset, $font_woff);
            if ($font_woff) {
                $font_svg = str_replace('.ttf', '.svg', $font_subset);
                $font_svg = self::convert_to_svg($font_subset, $font_svg);
                if ($font_svg) {
                    $font_hash = str_replace('.svg', '', $font_svg);
                    $font_hash = str_replace(Common::getPathOf('/', Common::P_PUBLIC), '', $font_hash);
                    $font_hash = Common::httpUri($font_hash, $_SERVER['HTTP_HOST']);
                    $content = self::get_css($font_name, $font_hash);
                    $css_path = self::split_hash($hash);
                    $css_path = $css_path.'/'.$hash.'.css';
                    self::save_css($content, Common::getPathOf($css_path, Common::P_PUBLIC));
                    return $css_path;
                }
            }
        }

        return false;
    }

    /**
     * 获取已经上传的字体list
     * @return array
     */
    static public function get_font_list() {
        $path = Common::getPathOf(self::FONT_UPLOAD_PATH.'/*.ttf', Common::P_PUBLIC);
        $list = glob($path);
        $out = array();
        foreach($list as $row) {
            $out[] = array('name' => str_replace('.ttf', '', basename($row)));
        }

        return $out;
    }

    /**
     * 生成css文件内容
     * @param $font_name
     * @param $font_hash
     * @return mixed
     */
    static public function get_css($font_name, $font_hash) {
        $path = Common::getPathOf(self::CSS_TEMPLATE, Common::P_PUBLIC);
        $content = file_get_contents($path);
        return str_replace(array('{:font_name}', '{:font_hash}'), array($font_name, $font_hash), $content);
    }

    /**
     * 保存生成的css文件
     * @param $content
     * @param $path
     */
    static public function save_css($content, $path) {
        Common::log('save css file:'.$path, 'font.log');
        $dir = dirname($path);
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        file_put_contents($path, $content);
    }

    /**
     * 生成压缩的ttf文件
     * @param $fontfile 源字体文件
     * @param $subset 包含裁剪的字符串
     * @param $hash 用于生成文件的hash
     * @return string 生成字体文件的完整路径
     */
    static public function subset_ttf($fontfile, $subset, $hash) {
        $font = \FontLib\Font::load($fontfile);
        $font->parse();

        $font->setSubset($subset);
        $font->reduce();

        $info = pathinfo($fontfile);
        $ext = empty($info['extension']) ? '' : '.' . $info['extension'];
        $name = basename($fontfile, $ext);
        $ral_path = self::FONT_CACHE_PATH.'/'.date('Y/m/d');
        $tmp_dir = Common::getPathOf($ral_path, Common::P_PUBLIC);
        if (!file_exists($tmp_dir)) {
            mkdir($tmp_dir, 0777, true);
        }
        $filename = $hash.$ext;
        $tmp = $tmp_dir . '/' . $filename;
        error_reporting(E_ERROR);
        $font->open($tmp, \FontLib\Binary_Stream::modeWrite);
        $font->encode(array("OS/2"));
        $font->close();
        error_reporting(E_NOTICE);

        return $tmp;
    }

    /**
     * 处理文件字体上传
     * @param $file
     * @return array
     */
    static public function handle_font_upload($file) {
        $info = pathinfo($file['name']);
        $ext = empty($info['extension']) ? '' : '.' . $info['extension'];
        $ext = strtolower($ext);
        if ($ext != '.ttf') {
            return array(false, 'only allow truetype file');
        }
        if ($file['error'] == 0 && !empty($file['tmp_name']) && is_uploaded_file($file['tmp_name'])) {
            $save_path = Common::getPathOf(self::FONT_UPLOAD_PATH, Common::P_PUBLIC);
            if (!file_exists($save_path)) {
                mkdir($save_path, 0777, true);
            }
            $font = \FontLib\Font::load($file['tmp_name']);
            $fullname = $font->getFontPostscriptName();
            $font->close();
            if (!preg_match('', $fullname, $matches)) {
                return array(false, 'truetype file not support');
            }
            $fontfile = $fullname.$ext;
            $bool = move_uploaded_file($file['tmp_name'], sprintf('%s/%s', $save_path, $fontfile));
            if ($bool) {
                return array(true, basename($fontfile, $ext));
            }
        }

        return array(false, 'upload font file failed.');
    }

    /**
     * 修改字体文件的family name
     * @param $fontfile 字体文件
     * @param $font_name 自定义的family name
     * @return bool
     */
    static public function fix_ttf_name($fontfile, $font_name) {
        $return = exec(sprintf(self::CONVERT_CMD, self::STDTTF_BIN, $fontfile, $font_name), $output, $return_val);
        Common::log(var_export($output, 1), 'font.log');
        if ($return_val === 0) {
            return true;
        }

        return false;
    }

    /**
     * 转换ttf为eot
     * @param $fontfile
     * @param $outfile
     * @return bool
     */
    static public function convert_to_eot($fontfile, $outfile) {
        $return = exec(sprintf(self::CONVERT_CMD, self::TTF2EOT_BIN, $fontfile, $outfile), $output, $return_val);
        Common::log(var_export($output, 1), 'font.log');
        if ($return_val === 0) {
            return $outfile;
        }

        return false;
    }

    /**
     * 转换ttf为woff
     * @param $fontfile
     * @param $outfile
     * @return bool
     */
    static public function convert_to_woff($fontfile, $outfile) {
        $return = exec(sprintf(self::CONVERT_CMD, self::TTF2WOFF_BIN, $fontfile, $outfile), $output, $return_val);
        Common::log(var_export($output, 1), 'font.log');
        if ($return_val === 0) {
            return $outfile;
        }

        return false;
    }

    /**
     * 转换ttf为svg
     * @param $fontfile
     * @param $outfile
     * @return bool
     */
    static public function convert_to_svg($fontfile, $outfile) {
        $return = exec(sprintf(self::CONVERT_CMD, self::TTF2SVG_BIN, $fontfile, $outfile), $output, $return_val);
        Common::log(var_export($output, 1), 'font.log');
        if ($return_val === 0) {
            return $outfile;
        }

        return false;
    }
}